package com.cafebabe.mongo.dao.impl;

import com.cafebabe.bean.BaseEnum;
import com.cafebabe.bean.MongoQuery;
import com.cafebabe.bean.Pager;
import com.cafebabe.mongo.dao.MongoDao;
import com.cafebabe.mongo.entity.MongoEntity;
import com.cafebabe.util.Reflections;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

@Repository
public abstract class MongoDaoImpl<T extends MongoEntity, PK extends Serializable> implements MongoDao<T, PK> {

    private static final int DEFAULT_SKIP = 0;
    private static final int DEFAULT_LIMIT = 200;
    private static String[] multiArgs = new String[]{"nin","in","all"};

    private Class<T> entityClass;

    public MongoDaoImpl(MongoTemplate mongoTemplate){
        this.mongoTemplate = mongoTemplate;
    }

    /**
     * spring mongodb　集成操作类　
     */
    @Resource
    protected MongoTemplate mongoTemplate;



    @SuppressWarnings("unchecked")
    public MongoDaoImpl() {
        this.entityClass = null;
        Class<?> c = getClass();
        Type type = c.getGenericSuperclass();//返回本类的父类,包含泛型参数信息
        if (type instanceof ParameterizedType) {
            Type[] parameterizedType = ((ParameterizedType) type)
                    .getActualTypeArguments();//泛型参数的实际类型
            this.entityClass = (Class<T>) parameterizedType[0];//第一个泛型
        }
    }

    protected Class<T> getEntityClass() {
        return entityClass;
    }

    @Override
    public List<T> find(Query query) {
        return mongoTemplate.find(query, entityClass);
    }

    @Override
    public T findOne(String query) {
        BasicQuery basicQuery  = new BasicQuery(query);
        return mongoTemplate.findOne(basicQuery, entityClass);
    }

    public List<T> findByQuery(String query){
        BasicQuery basicQuery  = new BasicQuery(query);
        return mongoTemplate.find(basicQuery, entityClass);
    }

    @Override
    public void update(Query query, Update update) {
        mongoTemplate.findAndModify(query, update, entityClass);
    }

    @Override
    public T save(T entity) {
        mongoTemplate.insert(entity);
        return entity;
    }

    @Override
    public T findById(String id) {
        System.out.println(entityClass);
        return mongoTemplate.findById(id, entityClass);
    }

    @Override
    public T findById(String id, String collectionName) {
        return mongoTemplate.findById(id, entityClass, collectionName);
    }


    @Override
    public Pager findPage(Pager pager, Query query) {
        long count = this.count(query);

        int i = (int)count;
        pager.setTotalCount(i);

        int pageNumber = pager.getPageNumber();
        int pageSize = pager.getPageSize();
        if(StringUtils.isNotEmpty(pager.getOrderBy())){
            query.with(new Sort(BaseEnum.OrderType.asc.equals(pager.getOrderBy())?Sort.Direction.ASC:Sort.Direction.DESC, pager.getOrderBy()));
        }
        query.skip((pageNumber - 1) * pageSize).limit(pageSize);
        List<T> rows = this.find(query);
        pager.setList(rows);
        return pager;
    }

    @Override
    public Pager findPagerByMap(Pager pager , Map<String,Map<String,Object>> smap){
        Criteria criteria = createCriteria(smap);
        Query query = new Query(criteria);
        return findPage(pager,query);
    }
    @Override
    public Pager findPager(Pager pager , List<MongoQuery> queryList){
        Criteria criteria = createCriteria(queryList);
        Query query = new Query(criteria);
        return findPage(pager,query);
    }



    public Criteria createCriteria(Map<String,Map<String,Object>> qMap) {

        Criteria c = new Criteria();
        List<Criteria> listC= new ArrayList<Criteria>();

        for(String op : qMap.keySet()){
            Map<String,?> rmap = qMap.get(op);
            for (String _s : rmap.keySet()) {
                Criteria criteria = Criteria.where(_s);
                Class[] argType = new Class[]{Object.class};
                Object[] args = new Object[]{rmap.get(_s)};

                if(ArrayUtils.contains(multiArgs,_s)){
                    argType = new Class[]{List.class};
                }
                if(StringUtils.equals(_s, "exists")){
                    argType = new Class[]{Boolean.class};
                }
                if(StringUtils.equals(_s, "type") || StringUtils.equals(_s, "size")) {
                    argType = new Class[]{Integer.class};
                }

                criteria = (Criteria) Reflections.invokeMethod(criteria,op,argType,args);
                listC.add(criteria);
            }
        }
        if(listC.size() > 0){
            Criteria [] cs = new Criteria[listC.size()];
            c.andOperator(listC.toArray(cs));
        }
        return c;
    }

    public Criteria createCriteria(List<MongoQuery> queryList) {

        Criteria c = new Criteria();
        for(MongoQuery query : queryList){
            String search = query.getSearch();
            Criteria criteria = Criteria.where(search);
            criteria = (Criteria) Reflections.invokeMethod(criteria,query.getMethod(),query.getArgsType(),query.getObject());
            c.andOperator(criteria);
        }
        return c;
    }
    @Override
    public long count(Query query) {
        return mongoTemplate.count(query, entityClass);
    }
}
